#include <assert.h>
#include "mesh.hpp"
#include <stdio.h>

Mesh::MeshEntry::MeshEntry()
{
  VtxB = INVALID_OGL_VALUE;
  IdxB = INVALID_OGL_VALUE;
  numIdx = 0;
  //  MaterialIndex = INVALID_MATERIAL;
}

Mesh::MeshEntry::MeshEntry::~MeshEntry()
{
  if (VtxB != INVALID_OGL_VALUE)
    glDeleteBuffers(1, &VtxB);
  if (IdxB != INVALID_OGL_VALUE)
    glDeleteBuffers(1, &IdxB);
}

/*
// Set up OpenGL internal states for a given MeshEntry
void Mesh::MeshEntry::Init(const std::vector<Vertex>& vtx,
			   const std::vector<unsigned int>& idx)
{
  numIdx = idx.size();

  glGenBuffers(1, &VB);
  glBindBuffer(GL_ARRAY_BUFFER, VB);
  glBufferData(GL_ARRAY_BUFFER, 
	       sizeof(Vertex)*vtx.size(), 
	       &vtx[0], GL_STATIC_DRAW);

  glGenBuffers(1, &IB);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IB);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, 
	       sizeof(unsigned int)*numIdx, 
	       &idx[0], GL_STATIC_DRAW);
}
*/
/*
void Mesh::ClearAllTexture()
{
  for(size_t i=0; i<_Textures.size(); i++)
    {
      if (_Textures[i] != NULL)
	{
	  delete _Texture[i];
	  _Texture[i] = NULL;
	}
    }
}
*/
bool Mesh::LoadMesh(const char* FilePath)
{
  //  ClearAllTexture();
  bool flag = false;

  Assimp::Importer Importer;

  printf("Loading file %s\n", FilePath);
  const aiScene* pScene = Importer.ReadFile(FilePath, aiProcess_Triangulate | aiProcess_GenSmoothNormals);
  // | aiProcess_FlipUVs
  if (pScene)
    flag = InitFromScene(pScene, FilePath);
  else
    printf("Error parsing '%s': '%s'\n", FilePath, Importer.GetErrorString());

  return flag;
}

bool Mesh::InitFromScene(const aiScene* pScene, const char* FilePath)
{
    _entries.resize(pScene->mNumMeshes);
    //    _textures.resize(pScene->mNumMaterials);

    printf("%d objs loaded\n", pScene->mNumMeshes);
    // Initialize the meshes in the scene one by one
    for (size_t i = 0; i < _entries.size(); i++)
      InitMeshPNT(i, pScene->mMeshes[i]);

    return true;//InitMaterials(pScene, Filename);
}

void Mesh::InitMeshPNT(unsigned int Index, const aiMesh* paiMesh)
{
  //  _entries[Index].MaterialIndex = paiMesh->mMaterialIndex;

  bool texture_flag = paiMesh->HasTextureCoords(0);
  if (texture_flag == false)
    printf("No available texture, default to all zeros\n");

  // Set up the position buffer
  glGenBuffers(1, &_entries[Index].VtxB);
  glBindBuffer(GL_ARRAY_BUFFER, _entries[Index].VtxB);
  glBufferData(GL_ARRAY_BUFFER, 
	       sizeof(aiVector3D)*paiMesh->mNumVertices, 
	       &(paiMesh->mVertices), GL_STATIC_DRAW);

  // Set up the normal buffer
  glGenBuffers(1, &_entries[Index].NrmB);
  glBindBuffer(GL_ARRAY_BUFFER, _entries[Index].NrmB);
  glBufferData(GL_ARRAY_BUFFER, 
	       sizeof(aiVector3D)*paiMesh->mNumVertices, 
	       &(paiMesh->mNormals), GL_STATIC_DRAW);

  // keep only the 2D uv coordinates
 //  std::vector<Vertex> vtx;
  std::vector<float> vtx;
  vtx.reserve(2*paiMesh->mNumVertices);
  const aiVector3D zero3D(0.0f, 0.0f, 0.0f);
  for(size_t i=0; i< paiMesh->mNumVertices; i++)
    {
      //      const aiVector3D* pPos = &(paiMesh->mVertices[i]);
      //      const aiVector3D* pNor = &(paiMesh->mNormals[i]);
      //      const aiVector3D* pTex = &zero3D;
      const aiVector3D* pTex = texture_flag?
      	&paiMesh->mTextureCoords[0][i]: &zero3D;

      vtx.push_back(pTex->x);
      vtx.push_back(pTex->y);
    }

  glGenBuffers(1, &_entries[Index].UVmB);
  glBindBuffer(GL_ARRAY_BUFFER, _entries[Index].UVmB);
  glBufferData(GL_ARRAY_BUFFER, 
	       sizeof(float)*vtx.size(), 
	       &(vtx[0]), GL_STATIC_DRAW);

  // Set up the index array
  std::vector<unsigned int> idx;
  for(size_t i=0; i<paiMesh->mNumFaces; i++)
    {
      const aiFace& f = paiMesh->mFaces[i];
      assert(f.mNumIndices == 3);
      idx.push_back(f.mIndices[0]);
      idx.push_back(f.mIndices[1]);
      idx.push_back(f.mIndices[2]);
    }

  glGenBuffers(1, &_entries[Index].IdxB);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _entries[Index].IdxB);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, 
	       sizeof(unsigned int)*idx.size(), 
	       &idx[0], GL_STATIC_DRAW);
  // Set the size of the index array
  _entries[Index].numIdx = idx.size();



  printf("Vertex buffer, size= %d\n", paiMesh->mNumVertices);
  for(int i=0; i< paiMesh->mNumVertices; i++)
    printf("%5f  %5f  %5f\n", paiMesh->mVertices[i].x, paiMesh->mVertices[i].y, paiMesh->mVertices[i].z);

  printf("UV buffer, size= %d\n", paiMesh->mNumVertices);
  for(int i=0; i< paiMesh->mNumVertices; i++)
    printf("%5f  %5f\n", paiMesh->mTextureCoords[0][i].x, paiMesh->mTextureCoords[0][i].y);
 
  printf("Normal buffer, size= %d\n", paiMesh->mNumVertices);
  for(int i=0; i< paiMesh->mNumVertices; i++)
    printf("%5f  %5f  %5f\n", paiMesh->mNormals[i].x, paiMesh->mNormals[i].y, paiMesh->mNormals[i].z);

  printf("Idx, size = %d\n", idx.size());
  for(int i=0; i< idx.size(); i++)
    printf("%5d\n", idx[i]);
  
}

void Mesh::RenderModel()
{
  glEnableVertexAttribArray(0);
  glEnableVertexAttribArray(1);
  glEnableVertexAttribArray(2);

  //  size_t sv = sizeof(Vertex);
  for(size_t i=0; i< _entries.size(); i++)
    {
      // 1st attribute buffer: vertices
      glBindBuffer(GL_ARRAY_BUFFER, _entries[i].VtxB);
      glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);

      // 2nd attribute buffer: UV maps:
      glBindBuffer(GL_ARRAY_BUFFER, _entries[i].UVmB);
      glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)0);

      // 2rd attribute buffer: normals:
      glBindBuffer(GL_ARRAY_BUFFER, _entries[i].NrmB);
      glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
      
      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _entries[i].IdxB);

      //TODO:figure out how to bind texture

      glDrawElements(GL_TRIANGLES, _entries[i].numIdx, 
		     GL_UNSIGNED_INT, (void*)0);
    }

  glDisableVertexAttribArray(0);
  glDisableVertexAttribArray(1);
  glDisableVertexAttribArray(2);
}
